{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# First-class functions\n", "\n", "A programming language is said to have first-class functions if it treats functions as [\"first-class citizens\"](https://en.wikipedia.org/wiki/First-class_citizen). Python has first-class functions.\n", "\n", "A first-class citizen is an entity which supports all the operations generally available to other entities. These operations typically include being passed as an argument, returned from a function, modified, and assigned to a variable.\n", "\n", "In Python, this means functions can do what objects can do." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the following example, functions are pass in as an input, variables can refer to functions, and functions have attributes." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def my_max(a, b):\n", " return a if a>b else b\n", "\n", "def my_sum(a, b):\n", " return a + b\n", "\n", "def reduce(lst, binOp):\n", " print(f\"Performing reduction with {binOp.__name__}\")\n", " ret = None\n", " for elem in lst:\n", " ret = elem if ret is None else binOp(ret, elem)\n", " return ret\n", " \n", "# print(dir(my_max))\n", "print(reduce([1,2,3,4], my_sum))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Closure\n", "\n", "A closure is a record storing a function together with an environment. The environment is a mapping associating each free variable of the function (variables that are used locally, but defined in an enclosing scope) with the value or reference to which the name was bound when the closure was created. Unlike a plain function, a closure allows the function to access those captured variables through the closure's copies of their values or references, even when the function is invoked outside their scope.\n", "\n", "\n", "This examples was inspired and copied from [Corey Schafer](https://www.youtube.com/channel/UCCezIgC97PvUuR4_gbFUs5g)'s Youtube channel." ] }, { "cell_type": "code", "execution_count": 55, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hello\n" ] } ], "source": [ "def outerFn(message):\n", " msg = message\n", " def innerFn():\n", " print(\"Hello\")\n", " return innerFn\n", "\n", "fn = outerFn(\"Goodbye\")\n", "fn()\n", "\n", "# print(msg) # msg goes out of scope. Cannot be accessed." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A closure is a function remembering a free variable. A *free variable* ($\\approx$ non-local variable) of a function is a variable not passed in as input nor created locally." ] }, { "cell_type": "code", "execution_count": 61, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "innerFn\n", "Goodbye\n" ] } ], "source": [ "def outerFn(message):\n", " msg = message\n", " def innerFn():\n", " print(msg) #msg is a \"free variable\"\n", " return innerFn\n", "\n", "fn = outerFn(\"Goodbye\")\n", "print(fn.__name__)\n", "fn() # innerFn remembers the msg variable.\n", "# print(msg) # msg goes out of scope. Cannot be accessed." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Closures are useful for passing things to do (a block of code to execute) as a function.\n", "\n", "In C++ and Java, closures were added relatively recently with the addition of lambdas. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Decorators\n", "\n", "A decorator is a function that takes another function as an argument, adds some kind of functionality, and then returns the augmented function." ] }, { "cell_type": "code", "execution_count": 71, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\"Hello\"\n" ] } ], "source": [ "def addQuotes(func):\n", " def wrapper():\n", " return '\"' + func() + '\"' # note. Closure is being used here.\n", " return wrapper\n", "\n", "def sayHello():\n", " return \"Hello\"\n", "\n", "fn = sayHello\n", "fn = addQuotes(fn)\n", "\n", "print(fn())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A decorator accomplishes the same with `@`." ] }, { "cell_type": "code", "execution_count": 76, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\"Hello\"\n" ] } ], "source": [ "def addQuotes(func):\n", " def wrapper():\n", " return '\"' + func() + '\"'\n", " return wrapper\n", "\n", "@addQuotes\n", "def sayHello():\n", " return \"Hello\"\n", "\n", "print(sayHello())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "More precisely, `@` is a syntactic shorthand for" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "@a\n", "def b():\n", " pass" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "is equivalent to" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def b():\n", " pass\n", "b = a(b)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Example: Using decorators to record when a function is beging executed or to crash (stop) the program for debugging purposes." ] }, { "cell_type": "code", "execution_count": 84, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Function someFunc was executed at 2021-08-19 15:35:33.869119.\n", "Result of some math computation is 9.424476.\n" ] } ], "source": [ "import datetime\n", "\n", "def timeLog(func):\n", " def wrapper():\n", " print(f\"Function {func.__name__} was executed at {datetime.datetime.now()}.\")\n", " return func()\n", " return wrapper\n", "\n", "def crash(func):\n", " def wrapper():\n", " print(f\"Function {func.__name__} called. We will now crash the program.\")\n", " 0/0\n", " return wrapper\n", "\n", "@timeLog\n", "def someFunc():\n", " return 3.141492\n", "\n", "print(f\"Result of some math computation is {2*someFunc()*1.5}.\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Lambda expression\n", "\n", "Lambda expressions to compactly define simple functions. Lambdas are also called anonymous functions." ] }, { "cell_type": "code", "execution_count": 89, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "3\n", "\n" ] } ], "source": [ "f = lambda x, y : x + y\n", "\n", "print(f(1,2))\n", "\n", "print(f.__name__)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Lambda expressions are often used to create inputs to a function." ] }, { "cell_type": "code", "execution_count": 91, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Performing reduction with \n", "3\n", "Performing reduction with \n", "6\n" ] } ], "source": [ "def reduce(lst, binOp):\n", " print(f\"Performing reduction with {binOp.__name__}\")\n", " ret = None\n", " for elem in lst:\n", " ret = elem if ret is None else binOp(ret, elem)\n", " return ret\n", " \n", "print(reduce([1,2,3], lambda a, b : a if a>b else b))\n", "print(reduce([1,2,3], lambda a, b : a + b))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Since the keyword `lambda` is reserved, variables corresponding to $\\lambda$ (such as scaling parameters for regularizers) are often named `lamda`. (Note the missing b.)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.6" } }, "nbformat": 4, "nbformat_minor": 4 }